package dilema_do_prisioneiro;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import util.MersenneTwister;

/**
 *
 * @author Guilherme
 */
public class DilemaPrisioneiro {

    private MersenneTwister random = new MersenneTwister(System.nanoTime());

    private List<IndividuoDP> populacao;
    private int tipo_individuo;
    private int tipo_fitness;
    private int tamanho_populacao = 30;
    private int tamanho_cromossomo = 30;
    private int tamanho_torneio = 8;
    private double taxa_cruzar = 0.825;// Influenciado pelo tam da população (a cada 1/tam_pop)
    private double taxa_cruzamento = 0.495;// Influenciado pelo tam do cromossomo (a cada 1/tam_cro)
    private double taxa_mutar = 0.099;// Influenciado pelo tam da população
    private double taxa_mutacao = 0.033;// Influenciado pelo tam do cromossomo

    public DilemaPrisioneiro(int tipo_ind, int tipo_fit) {
        this.setPopulacao(new ArrayList());
        this.gerarPopulacaoInicial(tipo_ind, tipo_fit);
    }

    //Inicio metodos AG
    public void gerarPopulacaoInicial(int tipo_ind, int tipo_fit) {
        this.tipo_individuo = tipo_ind % 3;
        this.tipo_fitness = tipo_fit % 2;
        this.completarPopulacao();
        this.imprimePopulacao(-1);
    }

    private IndividuoDP gerarNovoIndividuo() {
        boolean[] cod = new boolean[this.getTamanhoCromossomo()];
        for (int i = 0; i < cod.length; i++) {
            cod[i] = random.nextBoolean();
        }
        IndividuoDP ind = new IndividuoDP(this, cod, tipo_individuo, tipo_fitness);
        this.addParaPopulacao(ind);
        return ind;
    }

    public void cruzamento() {
        List<IndividuoDP> nova_populacao = new ArrayList();
        this.embaralharPopulacao();
        for (int i = 0; i < this.tamanhoAtualPopulacao() - 1; i += 2) {
            IndividuoDP ind1 = this.getIndividuo(i);
            IndividuoDP ind2 = this.getIndividuo(i + 1);
            if (random.nextBoolean(taxa_cruzar)) {
                nova_populacao.add(this.cruzar(ind1, ind2));
                nova_populacao.add(this.cruzar(ind1, ind2));
            } else {
                nova_populacao.add(ind1);
                nova_populacao.add(ind2);
            }
        }
        this.setPopulacao(nova_populacao);
        this.completarPopulacao();
    }

    private final int tam_corte_cruzamento = (int) Math.round(this.getTamanhoPopulacao() * taxa_cruzamento);

    private IndividuoDP cruzar(IndividuoDP pai, IndividuoDP mae) {
        IndividuoDP filho1 = new IndividuoDP(pai);
        IndividuoDP filho2 = new IndividuoDP(mae);
        int corte1 = random.nextInt(this.getTamanhoPopulacao() - tam_corte_cruzamento);
        int corte2 = corte1 + tam_corte_cruzamento;
        for (int x = corte1; x <= corte2; x++) {
            //int gene = x % this.getTamanhoPopulacao();//Ordem não importa (%)
            filho1.setGene(x, mae.getGene(x));
            filho2.setGene(x, pai.getGene(x));
        }
        if (filho2.getFitness() > filho1.getFitness()) {//Seleção natural do óvulo
            filho1 = filho2;
        }
        return filho1;
    }

    public void mutacao() {
        for (int i = 0; i < this.tamanhoAtualPopulacao(); i++) {
            if (random.nextBoolean(taxa_mutar)) {
                this.mutar(this.getIndividuo(i));
            }
        }
    }

    private final int tam_corte_mutacao = (int) Math.round(this.getTamanhoPopulacao() * taxa_mutacao);

    private void mutar(IndividuoDP ind) {
        int corte1 = random.nextInt(this.getTamanhoPopulacao() - tam_corte_mutacao);
        int corte2 = corte1 + tam_corte_mutacao;
        for (int x = corte1; x <= corte2; x++) {
            //int gene = x % this.getTamanhoPopulacao();//Ordem não importa (%)
            ind.setGene(x, !ind.getGene(x));
        }
    }

    public void torneio() {
        this.embaralharPopulacao();
        for (int i = 0; i < this.tamanhoAtualPopulacao(); i += tamanho_torneio - 1) {//-1 individuo na população por torneio
            List<IndividuoDP> torneio = this.getIndividuos(i, i + tamanho_torneio);
            IndividuoDP re = Collections.min(torneio);
            //System.out.println("Removido: " + re.getFitness());
            this.removeIndividuo(re);
        }
    }

    private void completarPopulacao() {
        while (this.tamanhoAtualPopulacao() < this.getTamanhoPopulacao()) {
            this.gerarNovoIndividuo();
        }
    }

    //Fim metodos AG
    private void embaralharPopulacao() {
        Collections.shuffle(populacao);
    }

    public void imprimePopulacao(int interacao) {
        System.out.println("Corte cruzamento: " + tam_corte_cruzamento + " Corte mutação: " + tam_corte_mutacao);
        System.out.println("---------------------------------- Interação: " + interacao);
        for (int j = 0; j < populacao.size(); j++) {
            System.out.println(j + " - " + populacao.get(j).getFitness());
        }
        System.out.println("");
    }

    public void setPopulacao(List<IndividuoDP> pop) {
        populacao = pop;
    }

    public void addParaPopulacao(IndividuoDP ind) {
        populacao.add(ind);
    }

    public IndividuoDP getIndividuo(int x) {
        return populacao.get(x);
    }

    public IndividuoDP getIndividuoAleatorio() {
        return populacao.get(random.nextInt(populacao.size()));
    }

    public IndividuoDP getIndividuoAleatorioDiferenteDe(IndividuoDP ind) {
        IndividuoDP ale;
        while ((ale = this.getIndividuoAleatorio()).equals(ind)) {
        }
        return ale;
    }

    public List<IndividuoDP> getIndividuos(int ini, int fim) {
        return populacao.subList(ini, (fim > populacao.size() ? populacao.size() : fim));
    }

    public IndividuoDP removeIndividuo(int x) {
        return populacao.remove(x);
    }

    public boolean removeIndividuo(IndividuoDP ind) {
        return populacao.remove(ind);
    }

    public int tamanhoAtualPopulacao() {
        return populacao.size();
    }

    public int getTamanhoPopulacao() {
        return tamanho_populacao;
    }

    public int getTamanhoCromossomo() {
        return tamanho_cromossomo;
    }

    public Double getMaiorFitness() {
        Double maior = Double.MIN_VALUE;
        for (int i = 0; i < this.tamanhoAtualPopulacao(); i++) {
            if (this.getIndividuo(i).getFitness() > maior) {
                maior = this.getIndividuo(i).getFitness();
            }
        }
        return maior;
    }

    public Double getMediaFitness() {
        Double soma = 0.0;
        for (int i = 0; i < this.tamanhoAtualPopulacao(); i++) {
            soma += this.getIndividuo(i).getFitness();
        }
        return (soma / this.tamanhoAtualPopulacao());
    }

}
